/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.revivius.jbtorrent.parse;

import com.revivius.jbtorrent.entity.FileInfo;
import com.revivius.jbtorrent.entity.Torrent;
import com.revivius.jbtorrent.entity.TorrentInfo;
import com.revivius.jbtorrent.util.HashUtil;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Revivius
 */
public class InfoParser implements KeyParser {
    
    private static final Logger LOG = Logger.getLogger(InfoParser.class.getName());
    
    private static final String FILES = "files";
    private static final String LENGTH = "length";
    private static final String NAME = "name";
    private static final String PIECE_LENGTH = "piece length";
    private static final String PIECES = "pieces";
    private static final String PRIVATE = "private";
    
    private static final String PATH = "path";
    
    private static final Map<String, InfoKeyParser> PARSERS = new HashMap<>();
    static {
        PARSERS.put(FILES, new FilesParser());
        PARSERS.put(LENGTH, new LengthParser());
        PARSERS.put(NAME, new NameParser());
        PARSERS.put(PIECE_LENGTH, new PieceLengthParser());
        PARSERS.put(PIECES, new PiecesParser());
        PARSERS.put(PRIVATE, new PrivateParser());
    }

    @Override
    public void parse(String key, Object value, Torrent torrent) throws Exception {
        
        Map<String, Object> infoMap = (Map<String, Object>) value;
        torrent.setInfo(createAndHashInfo(infoMap));
        
        
        infoMap.entrySet().stream().forEach(e -> {
            parseKey(e, torrent);
        });
    }
    
    private TorrentInfo createAndHashInfo(Map<String, Object> map) throws Exception {
        TorrentInfo info = new TorrentInfo();
        info.setHash(HashUtil.hash(map));
        
        return info;
    }
    
    private void parseKey(Map.Entry<String, Object> entry, Torrent torrent) {
        String key = entry.getKey();
        Object value = entry.getValue();

        KeyParser parser = PARSERS.get(key);
        if (parser == null) {
            LOG.log(Level.WARNING, "Unknown key: {0}, value is: {1}", new Object[]{key, value});
            torrent.addSkipped(key, value);
        } else {
            try {
                parser.parse(key, value, torrent);
            } catch (Exception ex) {
                LOG.log(Level.SEVERE, "Can not parse value of : " + key, ex);
                torrent.addSkipped(key, value);
            }
        }
    }

    private static abstract class InfoKeyParser implements KeyParser {

        @Override
        public void parse(String key, Object value, Torrent torrent) throws Exception {
            parseInfo(key, value, torrent.getInfo());
        }

        abstract void parseInfo(String key, Object value, TorrentInfo info) throws Exception;

    }

    private static class LengthParser extends InfoKeyParser {

        @Override
        void parseInfo(String key, Object value, TorrentInfo info) throws Exception {
            info.setLength(((BigInteger) value).longValue());
        }
        
    }
    
    private static class FilesParser extends InfoKeyParser {

        @Override
        void parseInfo(String key, Object value, TorrentInfo info) throws Exception {
            List<Map<String, Object>> list = (List<Map<String, Object>>) value;
            
            List<FileInfo> files = new ArrayList<>(list.size());
            
            list.stream().forEach(m -> {
                files.add(parseFileInfo(m));
            });
            
            info.setFiles(files);
        }
        
        private FileInfo parseFileInfo(Map<String, Object> map) {
            long length = ((BigInteger) map.get(LENGTH)).longValue();
            String path = KeyParser.fromList((List<byte[]>) map.get(PATH));
            
            return new FileInfo(path, length);
        }
        
    }
    
    private static class NameParser extends InfoKeyParser {

        @Override
        void parseInfo(String key, Object value, TorrentInfo info) throws Exception {
            info.setName(KeyParser.fromByteArray(value));
        }
        
    }
    
    private static class PieceLengthParser extends InfoKeyParser {

        @Override
        void parseInfo(String key, Object value, TorrentInfo info) throws Exception {
            info.setPieceLength(((BigInteger) value).longValue());
        }
        
    }
    
    private static class PiecesParser extends InfoKeyParser {

        @Override
        void parseInfo(String key, Object value, TorrentInfo info) throws Exception {
            byte[] piecesArr = (byte[]) value;
            
            int length = piecesArr.length;
            
            List<byte[]> pieces = new ArrayList<>();
            for (int i = 0; i < length; i += 20) {
                byte[] p = Arrays.copyOfRange(piecesArr, i, i + 20);
                pieces.add(p);
            }
            
            info.setPieces(pieces);
            
        }
        
    }
    
    private static class PrivateParser extends InfoKeyParser {

        @Override
        void parseInfo(String key, Object value, TorrentInfo info) throws Exception {
            int p = ((BigInteger) value).intValue();
            info.setPrivateFlag(p == 1);
        }
        
    }
    
}
